/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.framework.resource;

import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

import static axil.framework.Functions.*;


/**
 * A class that automatically handles finding a mechanism for loading resources
 * such as configuration files.
 */
public class ResourceManager {
	private static final ResourceManager instance = new ResourceManager();

	private List<ResourceLoader> loaders;
	private String home;


	private ResourceManager() {
		loaders = new ArrayList<ResourceLoader>();
		for (String spec : env("AXIL").split(",")) {
			if (first(spec) == '[') {
				String[] parts = spec.split("]");
				loaders.add(loader(deprefix(parts[0], "["), parts[1]));
			} else {
				/*
				 * In this case, no type was specified, so we assume it is for
				 * a filesystem.
				 */
				loaders.add(new FilesystemLoader(spec));
			}
		}
	}


	public static ResourceManager instance() {
		return instance;
	}


	private ResourceLoader loader(String type, String spec) {
		assert ! empty(type);
		assert ! empty(spec);

		if (in("/\\", last(spec))) {
			spec = spec.substring(0, spec.length() - 1);
		}
		if (type.equals("class")) {
			return new EmbeddedResourceLoader(spec);
		}
		if (type.equals("fs")) {
			return new FilesystemLoader(spec);
		}
		throw abort("That is not a known resource loader type.",
		            nv("type", type));
	}


	/**
	 * Get a stream for the named resource. The resource name contains the
	 * relative path for the resource, such as "data/axil.cfg". A null value
	 * is never returned; if the resource cannot be found, then an exception
	 * is thrown.
	 */
	public Reader reader(String name) {
		Reader reader = open(name);
		if (reader == null) {
			throw abort(
			    "A required resource cannot be found. This is likely a " +
			    "configuration problem in your environment. The listed " +
			    "resource loaders were consulted, in order, and none were " +
			    "able to find the resource.",
			    nv("loaders", this),
			    nv("resource", name));
		}
		return reader;
	}


	/**
	 * Get a stream for the named resource. The resource name contains the
	 * relative path for the resource, such as "data/axil.cfg". A null value
	 * is returned if the resource cannot be found by any of the loaders.
	 */
	public Reader open(String name) {
		for (ResourceLoader loader : loaders) {
			Reader reader = loader.reader(name);
			if (reader != null) {
				return new ReferenceReader(name, reader);
			}
		}
		return null;
	}


	/**
	 * Find a filesystem location which Axil considers "home" for loading test
	 * information. This cannot be from embedded resources, since we need to
	 * dynamically traverse the files found.
	 */
	public String home() {
		if (home == null) {
			for (ResourceLoader loader : loaders) {
				if (loader instanceof FilesystemLoader) {
					home = ((FilesystemLoader)loader).root();
					return home;
				}
			}
			throw abort("Axil cannot find his home.");
		}
		return home;
	}


	/**
	 * Return a string representation of this object. The string returned is
	 * never null.
	 */
	public String toString() {
		return separated(loaders);
	}
}
